package de.sendorian.app.forumArchive.dundjinni;

import static de.sendorian.util.matcher.FileTypeMatchers.archive;
import static org.apache.commons.lang.StringUtils.trimToEmpty;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import javax.annotation.PostConstruct;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hamcrest.Matcher;
import org.htmlparser.Node;
import org.htmlparser.NodeFilter;
import org.htmlparser.filters.HasAttributeFilter;
import org.htmlparser.filters.NotFilter;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.nodes.TagNode;
import org.htmlparser.nodes.TextNode;
import org.htmlparser.tags.ImageTag;
import org.htmlparser.tags.LinkTag;
import org.htmlparser.util.NodeList;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import de.sendorian.app.forumArchive.domain.Archive;
import de.sendorian.app.forumArchive.domain.File;
import de.sendorian.app.forumArchive.domain.Image;
import de.sendorian.app.forumArchive.domain.Link;
import de.sendorian.app.forumArchive.domain.Post;
import de.sendorian.app.forumArchive.finder.PostFinder;
import de.sendorian.app.forumArchive.renamer.FileRenamer;
import de.sendorian.util.htmlparser.LinkNameFilter;


@Component
public class DundjinniPostFinder extends PostFinder {

    private static final Logger LOG = Logger.getLogger(DundjinniPostFinder.class);

    private static final String FOOTER_INDICATOR = "__________________";

    private @Value("#{config.datePattern}") Pattern datePattern;
    private @Value("#{config.dateFormat}") SimpleDateFormat dateFormat;
    private @Value("#{config.defaultRenamer}") FileRenamer fileRenamer;

    private Pattern editedPattern;
    
    private static final Map<String, String> smileys = new HashMap<String, String>();
    
    static {
      smileys.put("smiley1.gif", ":)");
      smileys.put("smiley2.gif", ";)");
      smileys.put("smiley4.gif", ":D");
      smileys.put("smiley5.gif", ":s");
      smileys.put("smiley7.gif", ":x");
      smileys.put("smiley9.gif", ":$");
      smileys.put("smiley11.gif", "xx(");
      smileys.put("smiley17.gif", ":P");
      smileys.put("smiley18.gif", "8(");
      smileys.put("smiley19.gif", ":^(");
      smileys.put("smiley32.gif", "=D>");
      smileys.put("smiley36.gif", "LOL");
    }
    

    public DundjinniPostFinder() {
    }


    public DundjinniPostFinder(Matcher<Post> matcher) {
        super(matcher);
    }
    
    
    /**
     * {@inheritDoc}
     */
    @PostConstruct
    @Override
    protected void init() {
        super.init();
        editedPattern = Pattern.compile("Edited by .* on "+datePattern.pattern());
    }


    @Override
    protected String getAuthor(TagNode tr) {
        NodeList tds = tr.getChildren().extractAllNodesThatMatch(new TagNameFilter("td"));
        if (tds.size() > 1) {
            TagNode td = (TagNode) tds.elementAt(0);
            NodeFilter filter = new HasAttributeFilter("class", "bold");
            NodeList spans = td.getChildren().extractAllNodesThatMatch(filter);
            if (spans.size() > 0)
                return spans.elementAt(0).getFirstChild().getText();
        }
        return null;
    }


    @Override
    protected String getMessage(TagNode tr) {
        String message = getText(getSecondTd(tr));
        message = message.replace('\r', '\n');
        
        // remove multiple newlines
        while (message.contains("\n\n\n")) {
            message = message.replace("\n\n\n", "\n\n");
        }
        
        return message;
    }
    
    private String getText(Node node) {
        StringBuilder message = new StringBuilder();
        
        if (node instanceof TextNode) {
            String text = StringUtils.trimToEmpty(((TextNode) node).getText());
            if (text.contains(FOOTER_INDICATOR)) {
                String textAboveFooter = text.substring(0, text.indexOf(FOOTER_INDICATOR));
                if (!StringUtils.isEmpty(textAboveFooter)) {
                    message.append(textAboveFooter);
                }
                // footer found, quit processing of text here
                return null;
            }
            
            if (text != "") {
                message.append(text);
            }
        } else if (node instanceof ImageTag) {
            ImageTag image = (ImageTag) node;
            
            String url = de.sendorian.util.StringUtils.getNameFromFileURL(image.getImageURL());
            if (url.contains("smiley")) {
                if (smileys.containsKey(url)) {
                    message.append("["+smileys.get(url)+"]");
                } else {
                    message.append("["+url+"]");
                }
            } else if (!url.equals("quote_icon.gif")) {
                // not a smiley, no quote icon -> is an embedded image
                File imageFile = new File();
                imageFile.setUrl(image.getImageURL());
                // rename image to comply with the final name in images-list of the post
                fileRenamer.rename(imageFile);
                message.append("[IMG="+imageFile.getName()+"]");
            }
        } else if (node instanceof LinkTag) {
            LinkTag link = (LinkTag) node;
            
            if (!link.getLink().contains("edit_post.asp")) {
                message.append("[URL="+link.getLink()+"]");
                if (link.getChildCount() > 0) {
                    Node childNode = link.getChild(0);
                    if (childNode instanceof ImageTag) {
                        // get image tag inside this link
                        message.append(getText(childNode));
                    } else {
                        message.append(childNode.toPlainTextString());
                    }
                }
                message.append("[/URL]");
            }
        } else if (node instanceof TagNode) {
            TagNode tagNode = (TagNode) node;
            
            if (tagNode.getTagName().equals("BR")) {
                message.append('\n');
            } else if (!StringUtils.equals(tagNode.getAttribute("class"), "smText") && tagNode.getChildren() != null) {
                for (Node child : tagNode.getChildren()) {
                    String text = getText(child);
                    if (text == null) {
                        // reached footer line, quit processing
                        break;
                    }
                    message.append(text);
                }
            }
        }
        return message.toString();
    }
    
    
    @Override
    protected String getFooter(TagNode tr) {
        NodeList messageNodes = getMessageNodes(tr);
        if (messageNodes != null && messageNodes.size() > 0) {
            String message = trimToEmpty(messageNodes.asString());
            if (message.contains(FOOTER_INDICATOR)) {
                message = message.substring(message.indexOf(FOOTER_INDICATOR)+FOOTER_INDICATOR.length());
                return message.replace('\r', '\n');
            }
        }
        return null;
    }
    

    @Override
    protected Date getPostDate(TagNode tr) {
        TagNode td = getSecondTd(tr);
        if (td != null) {
            NodeList tables = td.getChildren().extractAllNodesThatMatch(new TagNameFilter("table"));
            if (tables.size() > 0) {
                TagNode table = (TagNode) tables.elementAt(0);
                NodeList spans = new NodeList();
                table.collectInto(spans, new HasAttributeFilter("class", "smText"));
                if (spans.size() > 0) {
                    String spanText = spans.elementAt(0).getFirstChild().getText();
                    return getDate(datePattern.matcher(spanText));
                }
            }
        }
        return null;
    }
    
    
    @Override
    protected Date getLastEditedDate(TagNode tr) {
        NodeList messageNodes = getMessageNodes(tr);
        if (messageNodes != null && messageNodes.size() > 0) {
            String message = trimToEmpty(messageNodes.asString());
            return getDate(editedPattern.matcher(message));
        }
        return null;
    }


    /**
     * Returns the Date that the matcher found.
     * 
     * @param matcher
     *            The matcher has to have 2 groups. The first one is the Date,
     *            the second one the time.
     * @return parsed Date or null if not found or a parse errror occured
     */
    private Date getDate(java.util.regex.Matcher matcher) {
        if (matcher.find()) {
            String date = matcher.group(1) + ' ' + matcher.group(2);
            try {
                return dateFormat.parse(date);
            } catch (ParseException e) {
                LOG.error("Unable to parse the date '" + date + "'", e);
            }
        }
        return null;
    }


    @Override
    protected String getPreviousPage(NodeList trs) {
        NodeList prevPage = trs.extractAllNodesThatMatch(new LinkNameFilter("&lt;&lt&nbsp;Prev"), true);
        if (prevPage.size() == 1) {
            TagNode link = (TagNode) prevPage.elementAt(0);
            return baseUrl + link.getAttribute("href");
        }
        return null;
    }


    @Override
    protected List<Archive> getArchives(TagNode row) {
        List<Archive> archives = new ArrayList<Archive>();

        NodeList linkNodes = getLinksInMessageNodes(row);
        if (linkNodes != null && linkNodes.size() > 0) {
            for (Node node : linkNodes.toNodeArray()) {
                TagNode link = (TagNode) node;
                String linkTarget = link.getAttribute("href");
                if (archive().matches(linkTarget)) {
                    Archive archive = new Archive();
                    archive.setUrl(linkTarget);
                    archives.add(archive);
                }
            }
        }
        return archives;
    }


    @Override
    protected List<Image> getImages(TagNode row) {
        List<Image> images = new ArrayList<Image>();
        Matcher<String> ignorableImageMatcher = new DundjinniIgnorableImageMatcher();

        NodeList messageNodes = getMessageNodes(row);
        if (messageNodes != null && messageNodes.size() > 0) {
            NodeList imageTags = messageNodes.extractAllNodesThatMatch(new TagNameFilter("img"), true);
            for (Node node : imageTags.toNodeArray()) {
                TagNode imageTag = (TagNode) node;
                String url = imageTag.getAttribute("src");
                if (!ignorableImageMatcher.matches(url)) {
                    Image image = new Image();
                    image.setUrl(url);
                    fileRenamer.rename(image);
                    images.add(image);
                }
            }
        }
        return images;
    }


    @Override
    protected List<Link> getLinks(TagNode row) {
        List<Link> links = new ArrayList<Link>();

        NodeList linkNodes = getLinksInMessageNodes(row);
        if (linkNodes != null && linkNodes.size() > 0) {
            for (Node node : linkNodes.toNodeArray()) {
                LinkTag linkNode = (LinkTag) node;
                String linkTarget = linkNode.getAttribute("href");
                linkTarget = StringEscapeUtils.unescapeHtml(linkTarget);
                boolean isInternal = getInternalLinkMatcher().matches(linkTarget);
                boolean isExternal = getExternalLinkMatcher().matches(linkTarget);
                
                if (isInternal || isExternal) {
                    Link link = new Link();
                    link.setTarget(linkTarget);
                    if (linkNode.getChildCount() > 0) {
                        Node childNode = linkNode.getChild(0);
                        if (childNode instanceof TextNode) {
                            link.setText(childNode.toPlainTextString());
                        }
                    }
                    
                    if (isInternal) {
                        link.setType(Link.Type.INTERNAL);
                    } else {
                        link.setType(Link.Type.EXTERNAL);
                    }

                    links.add(link);
                }
                // if not matched by both matchers, this link has to be ignored
            }
        }
        return links;
    }


    private TagNode getSecondTd(TagNode tr) {
        NodeList tds = tr.getChildren().extractAllNodesThatMatch(new TagNameFilter("td"));
        if (tds.size() > 1)
            return (TagNode) tds.elementAt(1);
        return null;
    }


    private NodeList getMessageNodes(TagNode row) {
        TagNode td = getSecondTd(row);
        if (td != null) {
            NodeFilter filter = new NotFilter(new TagNameFilter("table"));
            return td.getChildren().extractAllNodesThatMatch(filter);
        }
        return null;
    }


    private NodeList getLinksInMessageNodes(TagNode row) {
        NodeList messageNodes = getMessageNodes(row);
        if (messageNodes != null && messageNodes.size() > 0)
            return messageNodes.extractAllNodesThatMatch(new TagNameFilter("a"), true);
        return null;
    }
}
